﻿using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace DARE
{
    public sealed class CTransform : IEquatable<CTransform>
    {
        #region Fields

        private bool m_hasChanged = true;
        private float m_scale;
        private Matrix m_transform;
        private Vector3 m_position;
        private Quaternion m_rotation;

        #endregion

        #region Properties

        public Quaternion Rotation
        {
            get { return this.m_rotation; }
            set
            {
                if (this.m_rotation != value)
                {
                    this.m_rotation = value;
                    m_hasChanged = true;
                }
            }
        }

        public Vector3 Position
        {
            get { return this.m_position; }
            set
            {
                if (this.m_position != value)
                {
                    this.m_position = value;
                    m_hasChanged = true;
                }
            }
        }

        public float Scale
        {
            get { return this.m_scale; }
            set
            {
                if (this.m_scale != value)
                {
                    this.m_scale = value;
                    m_hasChanged = true;
                }
            }
        }

        [ContentSerializerIgnore]
        public Matrix Transform
        {
            get
            {
                if (m_hasChanged)
                {
                    UpdateTransformMatrix();
                    m_hasChanged = false;
                }
                return m_transform;
            }
        }

        #endregion

        #region CTOR

        public CTransform()
        {
            m_hasChanged = true;
        }

        public CTransform(CTransform _trs)
        {
            this.Position = _trs.Position;
            this.Rotation = _trs.Rotation;
            this.Scale = _trs.Scale;
        }

        public CTransform(Matrix _currentTransform)
        {
            m_hasChanged = true;
            Vector3 _scale;

            _scale.X = m_scale;
            bool result = _currentTransform.Decompose(out _scale, out m_rotation, out m_position);
            //Debug.Assert(result);
            if (!result)
            {
                m_rotation = Quaternion.Identity;
            }

            //if (!(Math.Round(_scale.X, 4) == Math.Round(_scale.Y, 4) && Math.Round(_scale.Y, 4) == Math.Round(_scale.Z, 4)))
            //    throw new Exception("The scale must be uniform");
            
            m_scale = _scale.X;
            m_rotation.Normalize();
        }

        public CTransform(Quaternion _rot, Vector3 _pos, Vector3 _scale)
        {
            m_rotation = _rot;
            m_position = _pos;

            if (!(Math.Round(_scale.X, 4) == Math.Round(_scale.Y, 4) && Math.Round(_scale.Y, 4) == Math.Round(_scale.Z, 4)))
                throw new Exception("The scale must be uniform");
            
            m_scale = _scale.X;
        }

        public CTransform(Quaternion _rot, Vector3 _pos, float _scale)
        {
            m_rotation = _rot;
            m_position = _pos;
            m_scale = _scale;
        }

        #endregion

        #region Method

        private void UpdateTransformMatrix()
        {
            this.m_transform = Matrix.CreateScale(m_scale) *
                Matrix.CreateFromQuaternion(m_rotation) *
                Matrix.CreateTranslation(m_position);
        }

        public static CTransform Lerp(CTransform prev, CTransform next, float amount)
        {
            CTransform _f = new CTransform();
            _f.Scale = MathHelper.Lerp(prev.Scale, next.Scale, amount);
            _f.Position = Vector3.Lerp(prev.Position, next.Position, amount);
            _f.Rotation = Quaternion.Slerp(prev.Rotation, next.Rotation, amount);
            return _f;
        }

        public static bool operator ==(CTransform a, CTransform b)
        {
            if ((object)a != (object)b)
            {
                if ((object)a == null || (object)b == null)
                    return false;
                return !a.Equals(b);
            }
            return true;
        }

        public static bool operator !=(CTransform a, CTransform b)
        {
            if ((object)a != (object)b)
            {
                if ((object)a == null || (object)b == null)
                    return true;
                return !a.Equals(b);
            }
            return false;
        }

        public static CTransform operator -(CTransform a, CTransform b)
        {
            return new CTransform(a.Rotation - b.Rotation, a.Position - b.Position, a.Scale - b.Scale);
        }

        public bool Equals(CTransform other)
        {
            return m_position == other.m_position && m_rotation == other.m_rotation && m_scale == other.m_scale;
        }

        public static bool Diff(CTransform a, CTransform b)
        {
            return Diff(a, b, 5);
        }
        
        public static bool Diff(CTransform a, CTransform b, int digit)
        {
            CTransform res = a - b;

            return !(Math.Round(res.m_position.Length(), digit) == 0
                    && Math.Round(res.m_scale, digit) == 0 && Math.Round(res.m_rotation.Length(), digit) == 0);
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #endregion
    }
}
